{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "cac06555-9ce8-4f01-bbef-3f8407f4b54d",
   "metadata": {},
   "source": [
    "# Multi-agent recruiting workflow \n",
    "> Make sure you run the Letta server before running this example using `letta server`\n",
    "\n",
    "Last tested with letta version `0.5.3`"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "aad3a8cc-d17a-4da1-b621-ecc93c9e2106",
   "metadata": {},
   "source": [
    "## Section 0: Setup a MemGPT client "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "7ccd43f2-164b-4d25-8465-894a3bb54c4b",
   "metadata": {},
   "outputs": [],
   "source": [
    "from letta_client import CreateBlock, Letta, MessageCreate\n",
    "\n",
    "client = Letta(base_url=\"http://localhost:8283\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "99a61da5-f069-4538-a548-c7d0f7a70227",
   "metadata": {},
   "source": [
    "## Section 1: Shared Memory Block \n",
    "Each agent will have both its own memory, and shared memory. The shared memory will contain information about the organization that the agents are all a part of. If one agent updates this memory, the changes will be propaged to the memory of all the other agents. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "7770600d-5e83-4498-acf1-05f5bea216c3",
   "metadata": {},
   "outputs": [],
   "source": [
    "org_description = \"The company is called AgentOS \" \\\n",
    "+ \"and is building AI tools to make it easier to create \" \\\n",
    "+ \"and deploy LLM agents.\"\n",
    "\n",
    "org_block = client.blocks.create(\n",
    "    label=\"company\",\n",
    "    value=org_description,\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "6c3d3a55-870a-4ff0-81c0-4072f783a940",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Block(value='The company is called AgentOS and is building AI tools to make it easier to create and deploy LLM agents.', limit=2000, template_name=None, template=False, label='company', description=None, metadata_={}, user_id=None, id='block-f212d9e6-f930-4d3b-b86a-40879a38aec4')"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "org_block"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8448df7b-c321-4d90-ba52-003930a513cb",
   "metadata": {},
   "source": [
    "## Section 2: Orchestrating Multiple Agents \n",
    "We'll implement a recruiting workflow that involves evaluating an candidate, then if the candidate is a good fit, writing a personalized email on the human's behalf. Since this task involves multiple stages, sometimes breaking the task down to multiple agents can improve performance (though this is not always the case). We will break down the task into: \n",
    "\n",
    "1. `eval_agent`: This agent is responsible for evaluating candidates based on their resume\n",
    "2. `outreach_agent`: This agent is responsible for writing emails to strong candidates\n",
    "3. `recruiter_agent`: This agent is responsible for generating leads from a database \n",
    "\n",
    "Much like humans, these agents will communicate by sending each other messages. We can do this by giving agents that need to communicate with other agents access to a tool that allows them to message other agents. "
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a065082a-d865-483c-b721-43c5a4d51afe",
   "metadata": {},
   "source": [
    "#### Evaluator Agent\n",
    "This agent will have tools to: \n",
    "* Read a resume \n",
    "* Submit a candidate for outreach (which sends the candidate information to the `outreach_agent`)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "c00232c5-4c37-436c-8ea4-602a31bd84fa",
   "metadata": {},
   "outputs": [],
   "source": [
    "def read_resume(self, name: str): \n",
    "    \"\"\"\n",
    "    Read the resume data for a candidate given the name\n",
    "\n",
    "    Args: \n",
    "        name (str): Candidate name \n",
    "\n",
    "    Returns: \n",
    "        resume_data (str): Candidate's resume data \n",
    "    \"\"\"\n",
    "    import os\n",
    "    filepath = os.path.join(\"data\", \"resumes\", name.lower().replace(\" \", \"_\") + \".txt\")\n",
    "    return open(filepath).read()\n",
    "\n",
    "def submit_evaluation(self, candidate_name: str, reach_out: bool, resume: str, justification: str): \n",
    "    \"\"\"\n",
    "    Submit a candidate for outreach. \n",
    "\n",
    "    Args: \n",
    "        candidate_name (str): The name of the candidate\n",
    "        reach_out (bool): Whether to reach out to the candidate\n",
    "        resume (str): The text representation of the candidate's resume \n",
    "        justification (str): Justification for reaching out or not\n",
    "    \"\"\"\n",
    "    from letta import create_client \n",
    "    client = create_client()\n",
    "    message = \"Reach out to the following candidate. \" \\\n",
    "    + f\"Name: {candidate_name}\\n\" \\\n",
    "    + f\"Resume Data: {resume}\\n\" \\\n",
    "    + f\"Justification: {justification}\"\n",
    "    # NOTE: we will define this agent later \n",
    "    if reach_out:\n",
    "        response = client.send_message(\n",
    "            agent_name=\"outreach_agent\", \n",
    "            role=\"user\", \n",
    "            message=message\n",
    "        ) \n",
    "    else: \n",
    "        print(f\"Candidate {candidate_name} is rejected: {justification}\")\n",
    "\n",
    "# TODO: add an archival andidate tool (provide justification) \n",
    "\n",
    "read_resume_tool = client.tools.upsert_from_function(func=read_resume) \n",
    "submit_evaluation_tool = client.tools.upsert_from_function(func=submit_evaluation)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "12482994-03f4-4dda-8ea2-6492ec28f392",
   "metadata": {},
   "outputs": [],
   "source": [
    "skills = \"Front-end (React, Typescript), software engineering \" \\\n",
    "+ \"(ideally Python), and experience with LLMs.\"\n",
    "eval_persona = f\"You are responsible to finding good recruiting \" \\\n",
    "+ \"candidates, for the company description. \" \\\n",
    "+ f\"Ideal canddiates have skills: {skills}. \" \\\n",
    "+ \"Submit your candidate evaluation with the submit_evaluation tool. \"\n",
    "\n",
    "eval_agent = client.agents.create(\n",
    "    name=\"eval_agent\", \n",
    "    memory_blocks=[\n",
    "        CreateBlock(\n",
    "            label=\"persona\",\n",
    "            value=eval_persona,\n",
    "        ),\n",
    "    ],\n",
    "    block_ids=[org_block.id],\n",
    "    tool_ids=[read_resume_tool.id, submit_evaluation_tool.id]\n",
    "    model=\"openai/gpt-4\",\n",
    "    embedding=\"openai/text-embedding-ada-002\",\n",
    ")\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "37c2d0be-b980-426f-ab24-1feaa8ed90ef",
   "metadata": {},
   "source": [
    "#### Outreach agent \n",
    "This agent will email candidates with customized emails. Since sending emails is a bit complicated, we'll just pretend we sent an email by printing it in the tool call. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "24e8942f-5b0e-4490-ac5f-f9e1f3178627",
   "metadata": {},
   "outputs": [],
   "source": [
    "def email_candidate(self, content: str): \n",
    "    \"\"\"\n",
    "    Send an email\n",
    "\n",
    "    Args: \n",
    "        content (str): Content of the email \n",
    "    \"\"\"\n",
    "    print(\"Pretend to email:\", content)\n",
    "    return\n",
    "\n",
    "email_candidate_tool = client.tools.upsert_from_function(func=email_candidate)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "87416e00-c7a0-4420-be71-e2f5a6404428",
   "metadata": {},
   "outputs": [],
   "source": [
    "outreach_persona = \"You are responsible for sending outbound emails \" \\\n",
    "+ \"on behalf of a company with the send_emails tool to \" \\\n",
    "+ \"potential candidates. \" \\\n",
    "+ \"If possible, make sure to personalize the email by appealing \" \\\n",
    "+ \"to the recipient with details about the company. \" \\\n",
    "+ \"You position is `Head Recruiter`, and you go by the name Bob, with contact info bob@gmail.com. \" \\\n",
    "+ \"\"\"\n",
    "Follow this email template: \n",
    "\n",
    "Hi <candidate name>, \n",
    "\n",
    "<content> \n",
    "\n",
    "Best, \n",
    "<your name> \n",
    "<company name> \n",
    "\"\"\"\n",
    "    \n",
    "outreach_agent = client.agents.create(\n",
    "    name=\"outreach_agent\", \n",
    "    memory_blocks=[\n",
    "        CreateBlock(\n",
    "            label=\"persona\",\n",
    "            value=outreach_persona,\n",
    "        ),\n",
    "    ],\n",
    "    block_ids=[org_block.id],\n",
    "    tool_ids=[email_candidate_tool.id]\n",
    "    model=\"openai/gpt-4\",\n",
    "    embedding=\"openai/text-embedding-ada-002\",\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f69d38da-807e-4bb1-8adb-f715b24f1c34",
   "metadata": {},
   "source": [
    "Next, we'll send a message from the user telling the `leadgen_agent` to evaluate a given candidate: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "f09ab5bd-e158-42ee-9cce-43f254c4d2b0",
   "metadata": {},
   "outputs": [],
   "source": [
    "response = client.agents.messages.send(\n",
    "    agent_id=eval_agent.id,\n",
    "    messages=[\n",
    "        MessageCreate(\n",
    "            role=\"user\",\n",
    "            content=\"Candidate: Tony Stark\",\n",
    "        )\n",
    "    ],\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "cd8f1a1e-21eb-47ae-9eed-b1d3668752ff",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "        <style>\n",
       "            .message-container, .usage-container {\n",
       "                font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;\n",
       "                max-width: 800px;\n",
       "                margin: 20px auto;\n",
       "                background-color: #1e1e1e;\n",
       "                border-radius: 8px;\n",
       "                overflow: hidden;\n",
       "                color: #d4d4d4;\n",
       "            }\n",
       "            .message, .usage-stats {\n",
       "                padding: 10px 15px;\n",
       "                border-bottom: 1px solid #3a3a3a;\n",
       "            }\n",
       "            .message:last-child, .usage-stats:last-child {\n",
       "                border-bottom: none;\n",
       "            }\n",
       "            .title {\n",
       "                font-weight: bold;\n",
       "                margin-bottom: 5px;\n",
       "                color: #ffffff;\n",
       "                text-transform: uppercase;\n",
       "                font-size: 0.9em;\n",
       "            }\n",
       "            .content {\n",
       "                background-color: #2d2d2d;\n",
       "                border-radius: 4px;\n",
       "                padding: 5px 10px;\n",
       "                font-family: 'Consolas', 'Courier New', monospace;\n",
       "                white-space: pre-wrap;\n",
       "            }\n",
       "            .json-key, .function-name, .json-boolean { color: #9cdcfe; }\n",
       "            .json-string { color: #ce9178; }\n",
       "            .json-number { color: #b5cea8; }\n",
       "            .internal-monologue { font-style: italic; }\n",
       "        </style>\n",
       "        <div class=\"message-container\">\n",
       "        \n",
       "            <div class=\"message\">\n",
       "                <div class=\"title\">INTERNAL MONOLOGUE</div>\n",
       "                <div class=\"content\"><span class=\"internal-monologue\">Checking the resume for Tony Stark to evaluate if he fits the bill for our needs.</span></div>\n",
       "            </div>\n",
       "            \n",
       "            <div class=\"message\">\n",
       "                <div class=\"title\">FUNCTION CALL</div>\n",
       "                <div class=\"content\"><span class=\"function-name\">read_resume</span>({<br>&nbsp;&nbsp;<span class=\"json-key\">\"name\"</span>: <span class=\"json-key\">\"Tony Stark\",<br>&nbsp;&nbsp;\"request_heartbeat\"</span>: <span class=\"json-boolean\">true</span><br>})</div>\n",
       "            </div>\n",
       "            \n",
       "            <div class=\"message\">\n",
       "                <div class=\"title\">FUNCTION RETURN</div>\n",
       "                <div class=\"content\">{<br>&nbsp;&nbsp;<span class=\"json-key\">\"status\"</span>: <span class=\"json-key\">\"Failed\",<br>&nbsp;&nbsp;\"message\"</span>: <span class=\"json-key\">\"Error calling function read_resume: [Errno 2] No such file or directory: 'data/resumes/tony_stark.txt'\",<br>&nbsp;&nbsp;\"time\"</span>: <span class=\"json-string\">\"2024-11-13 05:51:26 PM PST-0800\"</span><br>}</div>\n",
       "            </div>\n",
       "            \n",
       "            <div class=\"message\">\n",
       "                <div class=\"title\">INTERNAL MONOLOGUE</div>\n",
       "                <div class=\"content\"><span class=\"internal-monologue\">I couldn&#x27;t retrieve Tony&#x27;s resume. Need to handle this carefully to keep the conversation flowing.</span></div>\n",
       "            </div>\n",
       "            \n",
       "            <div class=\"message\">\n",
       "                <div class=\"title\">FUNCTION CALL</div>\n",
       "                <div class=\"content\"><span class=\"function-name\">send_message</span>({<br>&nbsp;&nbsp;<span class=\"json-key\">\"message\"</span>: <span class=\"json-string\">\"It looks like I'm having trouble accessing Tony Stark's resume at the moment. Can you provide more details about his qualifications?\"</span><br>})</div>\n",
       "            </div>\n",
       "            \n",
       "            <div class=\"message\">\n",
       "                <div class=\"title\">FUNCTION RETURN</div>\n",
       "                <div class=\"content\">{<br>&nbsp;&nbsp;<span class=\"json-key\">\"status\"</span>: <span class=\"json-key\">\"OK\",<br>&nbsp;&nbsp;\"message\"</span>: <span class=\"json-key\">\"None\",<br>&nbsp;&nbsp;\"time\"</span>: <span class=\"json-string\">\"2024-11-13 05:51:28 PM PST-0800\"</span><br>}</div>\n",
       "            </div>\n",
       "            </div>\n",
       "        <div class=\"usage-container\">\n",
       "            <div class=\"usage-stats\">\n",
       "                <div class=\"title\">USAGE STATISTICS</div>\n",
       "                <div class=\"content\">{<br>&nbsp;&nbsp;<span class=\"json-key\">\"completion_tokens\"</span>: <span class=\"json-number\">103</span>,<br>&nbsp;&nbsp;<span class=\"json-key\">\"prompt_tokens\"</span>: <span class=\"json-number\">4999</span>,<br>&nbsp;&nbsp;<span class=\"json-key\">\"total_tokens\"</span>: <span class=\"json-number\">5102</span>,<br>&nbsp;&nbsp;<span class=\"json-key\">\"step_count\"</span>: <span class=\"json-number\">2</span><br>}</div>\n",
       "            </div>\n",
       "        </div>\n",
       "        "
      ],
      "text/plain": [
       "LettaResponse(messages=[InternalMonologue(id='message-97a1ae82-f8f3-419f-94c4-263112dbc10b', date=datetime.datetime(2024, 11, 14, 1, 51, 26, 799617, tzinfo=datetime.timezone.utc), message_type='internal_monologue', internal_monologue='Checking the resume for Tony Stark to evaluate if he fits the bill for our needs.'), FunctionCallMessage(id='message-97a1ae82-f8f3-419f-94c4-263112dbc10b', date=datetime.datetime(2024, 11, 14, 1, 51, 26, 799617, tzinfo=datetime.timezone.utc), message_type='function_call', function_call=FunctionCall(name='read_resume', arguments='{\\n  \"name\": \"Tony Stark\",\\n  \"request_heartbeat\": true\\n}', function_call_id='call_wOsiHlU3551JaApHKP7rK4Rt')), FunctionReturn(id='message-97a2b57e-40c6-4f06-a307-a0e3a00717ce', date=datetime.datetime(2024, 11, 14, 1, 51, 26, 803505, tzinfo=datetime.timezone.utc), message_type='function_return', function_return='{\\n  \"status\": \"Failed\",\\n  \"message\": \"Error calling function read_resume: [Errno 2] No such file or directory: \\'data/resumes/tony_stark.txt\\'\",\\n  \"time\": \"2024-11-13 05:51:26 PM PST-0800\"\\n}', status='error', function_call_id='call_wOsiHlU3551JaApHKP7rK4Rt'), InternalMonologue(id='message-8e249aea-27ce-4788-b3e0-ac4c8401bc93', date=datetime.datetime(2024, 11, 14, 1, 51, 28, 360676, tzinfo=datetime.timezone.utc), message_type='internal_monologue', internal_monologue=\"I couldn't retrieve Tony's resume. Need to handle this carefully to keep the conversation flowing.\"), FunctionCallMessage(id='message-8e249aea-27ce-4788-b3e0-ac4c8401bc93', date=datetime.datetime(2024, 11, 14, 1, 51, 28, 360676, tzinfo=datetime.timezone.utc), message_type='function_call', function_call=FunctionCall(name='send_message', arguments='{\\n  \"message\": \"It looks like I\\'m having trouble accessing Tony Stark\\'s resume at the moment. Can you provide more details about his qualifications?\"\\n}', function_call_id='call_1DoFBhOsP9OCpdPQjUfBcKjw')), FunctionReturn(id='message-5600e8e7-6c6f-482a-8594-a0483ef523a2', date=datetime.datetime(2024, 11, 14, 1, 51, 28, 361921, tzinfo=datetime.timezone.utc), message_type='function_return', function_return='{\\n  \"status\": \"OK\",\\n  \"message\": \"None\",\\n  \"time\": \"2024-11-13 05:51:28 PM PST-0800\"\\n}', status='success', function_call_id='call_1DoFBhOsP9OCpdPQjUfBcKjw')], usage=LettaUsageStatistics(completion_tokens=103, prompt_tokens=4999, total_tokens=5102, step_count=2))"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "response"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "67069247-e603-439c-b2df-9176c4eba957",
   "metadata": {},
   "source": [
    "#### Providing feedback to agents \n",
    "Since MemGPT agents are persisted, we can provide feedback to agents that is used in future agent executions if we want to modify the future behavior. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "19c57d54-a1fe-4244-b765-b996ba9a4788",
   "metadata": {},
   "outputs": [],
   "source": [
    "feedback = \"Our company pivoted to foundation model training\"\n",
    "response = client.agents.messages.send(\n",
    "    agent_id=eval_agent.id,\n",
    "    messages=[\n",
    "        MessageCreate(\n",
    "            role=\"user\",\n",
    "            content=feedback,\n",
    "        )\n",
    "    ],\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "036b973f-209a-4ad9-90e7-fc827b5d92c7",
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "feedback = \"The company is also renamed to FoundationAI\"\n",
    "response = client.agents.messages.send(\n",
    "    agent_id=eval_agent.id,\n",
    "    messages=[\n",
    "        MessageCreate(\n",
    "            role=\"user\",\n",
    "            content=feedback,\n",
    "        )\n",
    "    ],\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "5d7a7633-35a3-4e41-b44a-be71067dd32a",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "        <style>\n",
       "            .message-container, .usage-container {\n",
       "                font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;\n",
       "                max-width: 800px;\n",
       "                margin: 20px auto;\n",
       "                background-color: #1e1e1e;\n",
       "                border-radius: 8px;\n",
       "                overflow: hidden;\n",
       "                color: #d4d4d4;\n",
       "            }\n",
       "            .message, .usage-stats {\n",
       "                padding: 10px 15px;\n",
       "                border-bottom: 1px solid #3a3a3a;\n",
       "            }\n",
       "            .message:last-child, .usage-stats:last-child {\n",
       "                border-bottom: none;\n",
       "            }\n",
       "            .title {\n",
       "                font-weight: bold;\n",
       "                margin-bottom: 5px;\n",
       "                color: #ffffff;\n",
       "                text-transform: uppercase;\n",
       "                font-size: 0.9em;\n",
       "            }\n",
       "            .content {\n",
       "                background-color: #2d2d2d;\n",
       "                border-radius: 4px;\n",
       "                padding: 5px 10px;\n",
       "                font-family: 'Consolas', 'Courier New', monospace;\n",
       "                white-space: pre-wrap;\n",
       "            }\n",
       "            .json-key, .function-name, .json-boolean { color: #9cdcfe; }\n",
       "            .json-string { color: #ce9178; }\n",
       "            .json-number { color: #b5cea8; }\n",
       "            .internal-monologue { font-style: italic; }\n",
       "        </style>\n",
       "        <div class=\"message-container\">\n",
       "        \n",
       "            <div class=\"message\">\n",
       "                <div class=\"title\">INTERNAL MONOLOGUE</div>\n",
       "                <div class=\"content\"><span class=\"internal-monologue\">Updating the company name to reflect the rebranding. This is important for future candidate evaluations.</span></div>\n",
       "            </div>\n",
       "            \n",
       "            <div class=\"message\">\n",
       "                <div class=\"title\">FUNCTION CALL</div>\n",
       "                <div class=\"content\"><span class=\"function-name\">core_memory_replace</span>({<br>&nbsp;&nbsp;<span class=\"json-key\">\"label\"</span>: <span class=\"json-key\">\"company\",<br>&nbsp;&nbsp;\"old_content\"</span>: <span class=\"json-key\">\"The company has pivoted to foundation model training.\",<br>&nbsp;&nbsp;\"new_content\"</span>: <span class=\"json-key\">\"The company is called FoundationAI and has pivoted to foundation model training.\",<br>&nbsp;&nbsp;\"request_heartbeat\"</span>: <span class=\"json-boolean\">true</span><br>})</div>\n",
       "            </div>\n",
       "            \n",
       "            <div class=\"message\">\n",
       "                <div class=\"title\">FUNCTION RETURN</div>\n",
       "                <div class=\"content\">{<br>&nbsp;&nbsp;<span class=\"json-key\">\"status\"</span>: <span class=\"json-key\">\"OK\",<br>&nbsp;&nbsp;\"message\"</span>: <span class=\"json-key\">\"None\",<br>&nbsp;&nbsp;\"time\"</span>: <span class=\"json-string\">\"2024-11-13 05:51:34 PM PST-0800\"</span><br>}</div>\n",
       "            </div>\n",
       "            \n",
       "            <div class=\"message\">\n",
       "                <div class=\"title\">INTERNAL MONOLOGUE</div>\n",
       "                <div class=\"content\"><span class=\"internal-monologue\">Now I have the updated company info, time to check in on Tony.</span></div>\n",
       "            </div>\n",
       "            \n",
       "            <div class=\"message\">\n",
       "                <div class=\"title\">FUNCTION CALL</div>\n",
       "                <div class=\"content\"><span class=\"function-name\">send_message</span>({<br>&nbsp;&nbsp;<span class=\"json-key\">\"message\"</span>: <span class=\"json-string\">\"Got it, the new name is FoundationAI! What about Tony Stark's background catches your eye for this role? Any particular insights on his skills in front-end development or LLMs?\"</span><br>})</div>\n",
       "            </div>\n",
       "            \n",
       "            <div class=\"message\">\n",
       "                <div class=\"title\">FUNCTION RETURN</div>\n",
       "                <div class=\"content\">{<br>&nbsp;&nbsp;<span class=\"json-key\">\"status\"</span>: <span class=\"json-key\">\"OK\",<br>&nbsp;&nbsp;\"message\"</span>: <span class=\"json-key\">\"None\",<br>&nbsp;&nbsp;\"time\"</span>: <span class=\"json-string\">\"2024-11-13 05:51:35 PM PST-0800\"</span><br>}</div>\n",
       "            </div>\n",
       "            </div>\n",
       "        <div class=\"usage-container\">\n",
       "            <div class=\"usage-stats\">\n",
       "                <div class=\"title\">USAGE STATISTICS</div>\n",
       "                <div class=\"content\">{<br>&nbsp;&nbsp;<span class=\"json-key\">\"completion_tokens\"</span>: <span class=\"json-number\">146</span>,<br>&nbsp;&nbsp;<span class=\"json-key\">\"prompt_tokens\"</span>: <span class=\"json-number\">6372</span>,<br>&nbsp;&nbsp;<span class=\"json-key\">\"total_tokens\"</span>: <span class=\"json-number\">6518</span>,<br>&nbsp;&nbsp;<span class=\"json-key\">\"step_count\"</span>: <span class=\"json-number\">2</span><br>}</div>\n",
       "            </div>\n",
       "        </div>\n",
       "        "
      ],
      "text/plain": [
       "LettaResponse(messages=[InternalMonologue(id='message-0adccea9-4b96-4cbb-b5fc-a9ef0120c646', date=datetime.datetime(2024, 11, 14, 1, 51, 34, 180327, tzinfo=datetime.timezone.utc), message_type='internal_monologue', internal_monologue='Updating the company name to reflect the rebranding. This is important for future candidate evaluations.'), FunctionCallMessage(id='message-0adccea9-4b96-4cbb-b5fc-a9ef0120c646', date=datetime.datetime(2024, 11, 14, 1, 51, 34, 180327, tzinfo=datetime.timezone.utc), message_type='function_call', function_call=FunctionCall(name='core_memory_replace', arguments='{\\n  \"label\": \"company\",\\n  \"old_content\": \"The company has pivoted to foundation model training.\",\\n  \"new_content\": \"The company is called FoundationAI and has pivoted to foundation model training.\",\\n  \"request_heartbeat\": true\\n}', function_call_id='call_5s0KTElXdipPidchUu3R9CxI')), FunctionReturn(id='message-a2f278e8-ec23-4e22-a124-c21a0f46f733', date=datetime.datetime(2024, 11, 14, 1, 51, 34, 182291, tzinfo=datetime.timezone.utc), message_type='function_return', function_return='{\\n  \"status\": \"OK\",\\n  \"message\": \"None\",\\n  \"time\": \"2024-11-13 05:51:34 PM PST-0800\"\\n}', status='success', function_call_id='call_5s0KTElXdipPidchUu3R9CxI'), InternalMonologue(id='message-91f63cb2-b544-4b2e-82b1-b11643df5f93', date=datetime.datetime(2024, 11, 14, 1, 51, 35, 841684, tzinfo=datetime.timezone.utc), message_type='internal_monologue', internal_monologue='Now I have the updated company info, time to check in on Tony.'), FunctionCallMessage(id='message-91f63cb2-b544-4b2e-82b1-b11643df5f93', date=datetime.datetime(2024, 11, 14, 1, 51, 35, 841684, tzinfo=datetime.timezone.utc), message_type='function_call', function_call=FunctionCall(name='send_message', arguments='{\\n  \"message\": \"Got it, the new name is FoundationAI! What about Tony Stark\\'s background catches your eye for this role? Any particular insights on his skills in front-end development or LLMs?\"\\n}', function_call_id='call_R4Erx7Pkpr5lepcuaGQU5isS')), FunctionReturn(id='message-813a9306-38fc-4665-9f3b-7c3671fd90e6', date=datetime.datetime(2024, 11, 14, 1, 51, 35, 842423, tzinfo=datetime.timezone.utc), message_type='function_return', function_return='{\\n  \"status\": \"OK\",\\n  \"message\": \"None\",\\n  \"time\": \"2024-11-13 05:51:35 PM PST-0800\"\\n}', status='success', function_call_id='call_R4Erx7Pkpr5lepcuaGQU5isS')], usage=LettaUsageStatistics(completion_tokens=146, prompt_tokens=6372, total_tokens=6518, step_count=2))"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "response"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "d04d4b3a-6df1-41a9-9a8e-037fbb45836d",
   "metadata": {},
   "outputs": [],
   "source": [
    "response = client.agents.messages.send(\n",
    "    agent_id=eval_agent.id,\n",
    "    messages=[\n",
    "        MessageCreate(\n",
    "            role=\"system\",\n",
    "            content=\"Candidate: Spongebob Squarepants\",\n",
    "        )\n",
    "    ],\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "c60465f4-7977-4f70-9a75-d2ddebabb0fa",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Block(value='The company is called AgentOS and is building AI tools to make it easier to create and deploy LLM agents.\\nThe company is called FoundationAI and has pivoted to foundation model training.', limit=2000, template_name=None, template=False, label='company', description=None, metadata_={}, user_id=None, id='block-f212d9e6-f930-4d3b-b86a-40879a38aec4')"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "client.agents.core_memory.get_block(agent_id=eval_agent.id, block_label=\"company\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "id": "a51c6bb3-225d-47a4-88f1-9a26ff838dd3",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Block(value='The company is called AgentOS and is building AI tools to make it easier to create and deploy LLM agents.', limit=2000, template_name=None, template=False, label='company', description=None, metadata_={}, user_id=None, id='block-f212d9e6-f930-4d3b-b86a-40879a38aec4')"
      ]
     },
     "execution_count": 21,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "client.agents.core_memory.get_block(agent_id=outreach_agent.id, block_label=\"company\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8d181b1e-72da-4ebe-a872-293e3ce3a225",
   "metadata": {},
   "source": [
    "## Section 3: Adding an orchestrator agent \n",
    "So far, we've been triggering the `eval_agent` manually. We can also create an additional agent that is responsible for orchestrating tasks. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "id": "80b23d46-ed4b-4457-810a-a819d724e146",
   "metadata": {},
   "outputs": [],
   "source": [
    "#re-create agents \n",
    "client.agents.delete(eval_agent.id)\n",
    "client.agents.delete(outreach_agent.id)\n",
    "\n",
    "org_block = client.blocks.create(\n",
    "    label=\"company\",\n",
    "    value=org_description,\n",
    ")\n",
    "\n",
    "eval_agent = client.agents.create(\n",
    "    name=\"eval_agent\", \n",
    "    memory_blocks=[\n",
    "        CreateBlock(\n",
    "            label=\"persona\",\n",
    "            value=eval_persona,\n",
    "        ),\n",
    "    ],\n",
    "    block_ids=[org_block.id],\n",
    "    tool_ids=[read_resume_tool.id, submit_evaluation_tool.id]\n",
    "    model=\"openai/gpt-4\",\n",
    "    embedding=\"openai/text-embedding-ada-002\",\n",
    ")\n",
    "\n",
    "outreach_agent = client.agents.create(\n",
    "    name=\"outreach_agent\", \n",
    "    memory_blocks=[\n",
    "        CreateBlock(\n",
    "            label=\"persona\",\n",
    "            value=outreach_persona,\n",
    "        ),\n",
    "    ],\n",
    "    block_ids=[org_block.id],\n",
    "    tool_ids=[email_candidate_tool.id]\n",
    "    model=\"openai/gpt-4\",\n",
    "    embedding=\"openai/text-embedding-ada-002\",\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a751d0f1-b52d-493c-bca1-67f88011bded",
   "metadata": {},
   "source": [
    "The `recruiter_agent` will be linked to the same `org_block` that we created before - we can look up the current data in `org_block` by looking up its ID: "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "id": "bf6bd419-1504-4513-bc68-d4c717ea8e2d",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "Block(value='The company is called AgentOS and is building AI tools to make it easier to create and deploy LLM agents.\\nThe company is called FoundationAI and has pivoted to foundation model training.', limit=2000, template_name=None, template=False, label='company', description=None, metadata_={}, user_id='user-00000000-0000-4000-8000-000000000000', id='block-f212d9e6-f930-4d3b-b86a-40879a38aec4')"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "client.blocks.retrieve(block_id=org_block.id)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "e2730626-1685-46aa-9b44-a59e1099e973",
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import Optional\n",
    "\n",
    "def search_candidates_db(self, page: int) -> Optional[str]: \n",
    "    \"\"\"\n",
    "    Returns 1 candidates per page. \n",
    "    Page 0 returns the first 1 candidate, \n",
    "    Page 1 returns the next 1, etc.\n",
    "    Returns `None` if no candidates remain. \n",
    "\n",
    "    Args: \n",
    "        page (int): The page number to return candidates from \n",
    "\n",
    "    Returns: \n",
    "        candidate_names (List[str]): Names of the candidates\n",
    "    \"\"\"\n",
    "    \n",
    "    names = [\"Tony Stark\", \"Spongebob Squarepants\", \"Gautam Fang\"]\n",
    "    if page >= len(names): \n",
    "        return None\n",
    "    return names[page]\n",
    "\n",
    "def consider_candidate(self, name: str): \n",
    "    \"\"\"\n",
    "    Submit a candidate for consideration. \n",
    "\n",
    "    Args: \n",
    "        name (str): Candidate name to consider \n",
    "    \"\"\"\n",
    "    from letta_client import Letta, MessageCreate\n",
    "    client = Letta(base_url=\"http://localhost:8283\")\n",
    "    message = f\"Consider candidate {name}\" \n",
    "    print(\"Sending message to eval agent: \", message)\n",
    "    response = client.send_message(\n",
    "        agent_id=eval_agent.id,\n",
    "        role=\"user\", \n",
    "        message=message\n",
    "    ) \n",
    "\n",
    "\n",
    "# create tools \n",
    "search_candidate_tool = client.tools.upsert_from_function(func=search_candidates_db)\n",
    "consider_candidate_tool = client.tools.upsert_from_function(func=consider_candidate)\n",
    "\n",
    "# create recruiter agent\n",
    "recruiter_agent = client.agents.create(\n",
    "    name=\"recruiter_agent\", \n",
    "    memory_blocks=[\n",
    "        CreateBlock(\n",
    "            label=\"persona\",\n",
    "            value=\"You run a recruiting process for a company. \" \\\n",
    "            + \"Your job is to continue to pull candidates from the \" \n",
    "            + \"`search_candidates_db` tool until there are no more \" \\\n",
    "            + \"candidates left. \" \\\n",
    "            + \"For each candidate, consider the candidate by calling \"\n",
    "            + \"the `consider_candidate` tool. \" \\\n",
    "            + \"You should continue to call `search_candidates_db` \" \\\n",
    "            + \"followed by `consider_candidate` until there are no more \" \\\n",
    "            \" candidates. \",\n",
    "        ),\n",
    "    ],\n",
    "    block_ids=[org_block.id],\n",
    "    tool_ids=[search_candidate_tool.id, consider_candidate_tool.id],\n",
    "    model=\"openai/gpt-4\",\n",
    "    embedding=\"openai/text-embedding-ada-002\"\n",
    ")\n",
    "    \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "ecfd790c-0018-4fd9-bdaf-5a6b81f70adf",
   "metadata": {},
   "outputs": [],
   "source": [
    "response = client.agents.messages.send(\n",
    "    agent_id=recruiter_agent.id,\n",
    "    messages=[\n",
    "        MessageCreate(\n",
    "            role=\"system\",\n",
    "            content=\"Run generation\",\n",
    "        )\n",
    "    ],\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "8065c179-cf90-4287-a6e5-8c009807b436",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "        <style>\n",
       "            .message-container, .usage-container {\n",
       "                font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;\n",
       "                max-width: 800px;\n",
       "                margin: 20px auto;\n",
       "                background-color: #1e1e1e;\n",
       "                border-radius: 8px;\n",
       "                overflow: hidden;\n",
       "                color: #d4d4d4;\n",
       "            }\n",
       "            .message, .usage-stats {\n",
       "                padding: 10px 15px;\n",
       "                border-bottom: 1px solid #3a3a3a;\n",
       "            }\n",
       "            .message:last-child, .usage-stats:last-child {\n",
       "                border-bottom: none;\n",
       "            }\n",
       "            .title {\n",
       "                font-weight: bold;\n",
       "                margin-bottom: 5px;\n",
       "                color: #ffffff;\n",
       "                text-transform: uppercase;\n",
       "                font-size: 0.9em;\n",
       "            }\n",
       "            .content {\n",
       "                background-color: #2d2d2d;\n",
       "                border-radius: 4px;\n",
       "                padding: 5px 10px;\n",
       "                font-family: 'Consolas', 'Courier New', monospace;\n",
       "                white-space: pre-wrap;\n",
       "            }\n",
       "            .json-key, .function-name, .json-boolean { color: #9cdcfe; }\n",
       "            .json-string { color: #ce9178; }\n",
       "            .json-number { color: #b5cea8; }\n",
       "            .internal-monologue { font-style: italic; }\n",
       "        </style>\n",
       "        <div class=\"message-container\">\n",
       "        \n",
       "            <div class=\"message\">\n",
       "                <div class=\"title\">INTERNAL MONOLOGUE</div>\n",
       "                <div class=\"content\"><span class=\"internal-monologue\">New user logged in. Excited to get started!</span></div>\n",
       "            </div>\n",
       "            \n",
       "            <div class=\"message\">\n",
       "                <div class=\"title\">FUNCTION CALL</div>\n",
       "                <div class=\"content\"><span class=\"function-name\">send_message</span>({<br>&nbsp;&nbsp;<span class=\"json-key\">\"message\"</span>: <span class=\"json-string\">\"Welcome! I'm thrilled to have you here. Let’s dive into what you need today!\"</span><br>})</div>\n",
       "            </div>\n",
       "            \n",
       "            <div class=\"message\">\n",
       "                <div class=\"title\">FUNCTION RETURN</div>\n",
       "                <div class=\"content\">{<br>&nbsp;&nbsp;<span class=\"json-key\">\"status\"</span>: <span class=\"json-key\">\"OK\",<br>&nbsp;&nbsp;\"message\"</span>: <span class=\"json-key\">\"None\",<br>&nbsp;&nbsp;\"time\"</span>: <span class=\"json-string\">\"2024-11-13 05:52:14 PM PST-0800\"</span><br>}</div>\n",
       "            </div>\n",
       "            </div>\n",
       "        <div class=\"usage-container\">\n",
       "            <div class=\"usage-stats\">\n",
       "                <div class=\"title\">USAGE STATISTICS</div>\n",
       "                <div class=\"content\">{<br>&nbsp;&nbsp;<span class=\"json-key\">\"completion_tokens\"</span>: <span class=\"json-number\">48</span>,<br>&nbsp;&nbsp;<span class=\"json-key\">\"prompt_tokens\"</span>: <span class=\"json-number\">2398</span>,<br>&nbsp;&nbsp;<span class=\"json-key\">\"total_tokens\"</span>: <span class=\"json-number\">2446</span>,<br>&nbsp;&nbsp;<span class=\"json-key\">\"step_count\"</span>: <span class=\"json-number\">1</span><br>}</div>\n",
       "            </div>\n",
       "        </div>\n",
       "        "
      ],
      "text/plain": [
       "LettaResponse(messages=[InternalMonologue(id='message-8c8ab238-a43e-4509-b7ad-699e9a47ed44', date=datetime.datetime(2024, 11, 14, 1, 52, 14, 780419, tzinfo=datetime.timezone.utc), message_type='internal_monologue', internal_monologue='New user logged in. Excited to get started!'), FunctionCallMessage(id='message-8c8ab238-a43e-4509-b7ad-699e9a47ed44', date=datetime.datetime(2024, 11, 14, 1, 52, 14, 780419, tzinfo=datetime.timezone.utc), message_type='function_call', function_call=FunctionCall(name='send_message', arguments='{\\n  \"message\": \"Welcome! I\\'m thrilled to have you here. Let’s dive into what you need today!\"\\n}', function_call_id='call_2OIz7t3oiGsUlhtSneeDslkj')), FunctionReturn(id='message-26c3b7a3-51c8-47ae-938d-a3ed26e42357', date=datetime.datetime(2024, 11, 14, 1, 52, 14, 781455, tzinfo=datetime.timezone.utc), message_type='function_return', function_return='{\\n  \"status\": \"OK\",\\n  \"message\": \"None\",\\n  \"time\": \"2024-11-13 05:52:14 PM PST-0800\"\\n}', status='success', function_call_id='call_2OIz7t3oiGsUlhtSneeDslkj')], usage=LettaUsageStatistics(completion_tokens=48, prompt_tokens=2398, total_tokens=2446, step_count=1))"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "response"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "4639bbca-e0c5-46a9-a509-56d35d26e97f",
   "metadata": {},
   "outputs": [],
   "source": [
    "client.agents.delete(agent_id=eval_agent.id)\n",
    "client.agents.delete(agent_id=outreach_agent.id)\n",
    "client.agents.delete(agent_id=recruiter_agent.id)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "letta",
   "language": "python",
   "name": "letta"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
